﻿using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

namespace Nijie.Network
{
    public class SyncManager : NetworkBehaviour
    {
        [SerializeField]
        bool onlySyncOnceOperation = true;
        [SerializeField]
        float syncDeltTime = 1;
        [SerializeField]
        bool log = true;
        short msgID = 1011;
        short ipMsgID = 1010;
        NetworkClient client;
        //static Dictionary<string, ISyncListener> listeners = new Dictionary<string, ISyncListener>();
        static Dictionary<string, ISyncListener> listeners = new Dictionary<string, ISyncListener>();
        static List<Data> data = new List<Data>();
        List<string> addresses = new List<string>();
        public delegate void Lost(string address);
        public static Lost losts;
        public static List<string> Addresses
        {
            get
            {
                if (Instance == null) return new List<string>();
                else return Instance.addresses;
            }
        }
        public void OnEnable()
        {
            RegisterListener();
            if (log)
                Debug.Log("This IP is " + Address);
        }
        public void RegisterListener()
        {
            StartCoroutine(InitClient());
        }
        void SendToAllClient(short msgID, MessageBase msg)
        {
            int[] ips = NetworkServer.connections
                 .Where(c => c != null && c.connectionId != client.connection.connectionId)
                 .Select(c => c.connectionId).ToArray();
            if (ips == null || ips.Length == 0)
                return;
            else
                foreach (int ip in ips)
                {
                    NetworkServer.SendToClient(ip, msgID, msg);
                }
        }
        IEnumerator InitClient()
        {
            UnRegisterHandler();
            while (client == null)
            {
                if (NetworkClient.allClients.Count > 0)
                {
                    client = NetworkClient.allClients[0];
                    if (!IsServer)
                        client.RegisterHandler(ipMsgID, IpMessageHandler);
                    client.RegisterHandler(msgID, MessageHandler);
                    //if (log)
                    Debug.LogWarning(string.Format("Start as {0}. ", IsServer ? "Server" : "Client"));
                    if (IsServer)
                        NetworkServer.RegisterHandler(msgID, MessageHandler);
                }
                yield return new WaitForEndOfFrame();
            }
        }
        void UnRegisterHandler()
        {
            if (NetworkServer.handlers.ContainsKey(msgID))
                NetworkServer.UnregisterHandler(msgID);
            if (client != null)
            {
                if (client.handlers.ContainsKey(msgID))
                    client.UnregisterHandler(msgID);
                client = null;
            }
        }
        float time = 0;
        private void LateUpdate()
        {
            //Debug.Log(IsServer);
            time += Time.deltaTime;
            if (time > syncDeltTime)
            {
                Sync();
                time = 0;
            }
            //Debug.Log(addresses.Count);
        }
        private void Sync()
        {
            if (data.Count == 0) return;
            RemoteDevice thisDevice = new RemoteDevice(Address, IsServer);
            if (isServer)
            {
                UpdateAddresses();
                Message msg = new Message(log, thisDevice);
                IpMessage ipMsg = new IpMessage(addresses);
                //new Thread(() => {
                SendToAllClient(msgID, msg);
                SendToAllClient(ipMsgID, ipMsg);
                //}).Start();
            }
            else
            {
                Message msg = new Message(log, thisDevice);
                client.Send(msgID, msg);
            }
            data.Clear();
        }
        void UpdateAddresses()
        {
            List<string> connections = new List<string>();
            if (NetworkServer.connections != null)
                foreach (NetworkConnection connection in NetworkServer.connections)
                    if (connection != null)
                        connections.Add(connection.address);
            foreach (string address in addresses)
            {
                bool exist = false;
                foreach (string connection in connections)
                    if (connection.Equals(address))
                        exist = true;
                if (!exist)
                    if (losts != null) losts(address);
            }
            addresses.Clear();
            foreach (string address in connections)
            {
                if (address.Equals("localClient"))
                    addresses.Add(Address);
                else
                {
                    string[] adds = address.Split(':');
                    addresses.Add(adds[adds.Length - 1]);
                }
            }
        }
        void IpMessageHandler(NetworkMessage ipMsg)
        {
            IpMessage msg = ipMsg.ReadMessage<IpMessage>();
            NetworkReader reader = msg.Reader;
            addresses.Clear();
            int count = reader.ReadInt32();
            for (int i = 0; i < count; i++)
            {
                addresses.Add(reader.ReadString());
            }
        }
        void MessageHandler(NetworkMessage netMsg)
        {
            Message msg = netMsg.ReadMessage<Message>();
            NetworkReader reader = msg.Reader;
            string ipAddress = reader.ReadString();
            if (log)
                Debug.Log("Message from " + ipAddress);
            if (ipAddress.Equals(Address))
            {
                return;
            }
            bool isServer = reader.ReadBoolean();
            RemoteDevice device = new RemoteDevice(ipAddress, isServer);
            int count = (int)reader.ReadPackedUInt32();
            byte[][] d = new byte[count][];
            if (log)
                Debug.Log(string.Format("{0} Message Deserialize.", count));
            for (int c = 0; c < count; c++)
            {
                byte[] bytes = reader.ReadBytesAndSize();
                d[c] = bytes;
                NetworkWriter nw = new NetworkWriter(bytes);
                NetworkReader r = new NetworkReader(nw);
                string lisID = r.ReadString();
                ISyncListener listener;
                bool hasKey = listeners.TryGetValue(lisID, out listener);
                if (log)
                    Debug.Log(string.Format("Listener[{0},{3}]: {1} is reciving messages.[Length: {2}]",
                        c,
                        lisID,
                        bytes.Length,
                        hasKey
                        ));
                if (hasKey)
                    listener.OnDeserialize(r, device);
            }
            if (!isServer && IsServer)
            {
                Message newMsg = new Message(device, d);
                SendToAllClient(msgID, newMsg);
            }
        }
        public static byte[] SyncListenerData(ISyncListener listener)
        {
            if (Instance.log) Debug.Log(string.Format("Sync Listener: {0}", listener.ID));
            if (!listeners.ContainsKey(listener.ID))
            {
                Debug.LogError("Unregisted listener cannot be synced");
                return null;
            }
            if (Instance.onlySyncOnceOperation)
            {
                int tid = -1;
                for (int i = 0; i < data.Count; i++)
                {
                    if (data[i].ListenerID.Equals(listener.ID))
                    {
                        tid = i;
                    }
                }
                if (tid >= 0)
                    data.RemoveAt(tid);
            }
            NetworkWriter classWriter = new NetworkWriter();
            classWriter.Write(listener.ID);
            listener.OnSerialize(classWriter);
            byte[] bytes = classWriter.ToArray();
            data.Add(new Data(listener.ID, bytes));
            return bytes;
        }

        class Message : MessageBase
        {
            bool log = false;
            RemoteDevice device;
            public Message() { }
            public Message(bool log, RemoteDevice thisDevice)
            {
                device = thisDevice;
                this.log = log;
                if (log)
                    Debug.Log("Message Serialize. ");
            }
            byte[][] otherData;
            public Message(RemoteDevice device, byte[][] data)
            {
                this.device = device;
                otherData = data;
            }
            public override void Serialize(NetworkWriter writer)
            {
                writer.Write(device.Address);
                writer.Write(device.IsHost);
                if (log)
                {
                    if (otherData != null)
                        Debug.Log(string.Format("Transmit data length: {0}", data.Count));
                    else
                        Debug.Log(string.Format("Sync data length: {0}", data.Count));
                }
                if (otherData != null)
                {
                    writer.WritePackedUInt32((uint)otherData.Length);
                    foreach (byte[] d in otherData)
                    {
                        writer.WriteBytesAndSize(d, d.Length);
                    }
                }
                else
                {
                    writer.WritePackedUInt32((uint)data.Count);
                    foreach (Data d in data)
                    {
                        writer.WriteBytesAndSize(d.Bytes, d.Bytes.Length);
                    }
                }
            }
            public override void Deserialize(NetworkReader reader)
            {
                Reader = reader;
            }
            public NetworkReader Reader
            {
                private set;
                get;
            }
        }
        class IpMessage : MessageBase
        {
            public IpMessage() { }
            public NetworkReader Reader
            {
                private set;
                get;
            }
            List<string> addresses;
            public IpMessage(List<string> addresses)
            {
                this.addresses = addresses;
            }
            public override void Deserialize(NetworkReader reader)
            {
                Reader = reader;
            }

            public override void Serialize(NetworkWriter writer)
            {
                int count = 0;
                if (addresses != null)
                    count = addresses.Count;
                writer.Write(count);
                for (int i = 0; i < count; i++)
                {
                    writer.Write(addresses[i]);
                    //Debug.Log(addresses[i]);
                }
            }
        }
        public static void AddListener(ISyncListener listener)
        {
            //string className = matchName == null ? listener.GetType().Name : matchName;
            try
            {
                if (listeners.ContainsKey(listener.ID))
                {
                    Debug.LogError("The listener or listener's ID existed. ");
                    return;
                }
                listeners.Add(listener.ID, listener);
                if (Instance.log)
                    Debug.Log("Add listener: " + listener.ID);
            }
            catch (Exception e)
            {
                Debug.LogWarning(e);
            }
        }
        public static void RemoveListener(ISyncListener listener)
        {
            try
            {
                listeners.Remove(listener.ID);
                if (Instance.log)
                    Debug.Log("Remove listener: " + listener.ID);
            }
            catch (Exception e)
            {
                Debug.Log(e);
            }
        }
        private static SyncManager instance;
        public static SyncManager Instance
        {
            get
            {
                return instance;
            }
        }
        /// <summary>
        /// Returns whether the instance has been initialized or not.
        /// </summary>
        public static bool IsInitialized
        {
            get
            {
                return instance != null;
            }
        }
        /// <summary>
        /// Base awake method that sets the singleton's unique instance.
        /// </summary>
        protected virtual void Awake()
        {
            if (instance != null)
            {
                Debug.LogErrorFormat("Trying to instantiate a second instance of singleton class {0}", GetType().Name);
            }
            else
            {
                instance = this;
            }
        }
        protected virtual void OnDestroy()
        {
            if (instance == this)
            {
                instance = null;
            }
            listeners.Clear();
        }
        //string ipAddress;
        public static string Address
        {
            get
            {
                try
                {
                    return NetworkConnectionSettings.IPV4;
                }
                catch (Exception e) { return null; }
            }
        }
        public static string HostIP
        {
            get
            {
                if (Addresses.Count > 0)
                    return Addresses[0];
                else
                    return null;
            }
        }
        public static bool IsServer
        {
            get
            {
                if (Instance == null) return false;
                return Instance.isServer;
            }
        }
        public static float SyncDeltaTime { set { Instance.syncDeltTime = value; } get { return Instance.syncDeltTime; } }
    }
    public struct RemoteDevice
    {
        public string Address;
        public bool IsHost;
        public RemoteDevice(string address, bool isHost)
        {
            Address = address;
            IsHost = isHost;
        }
    }
    struct Data : ICloneable
    {
        public string ListenerID;
        public byte[] Bytes;
        public Data(string id, byte[] bytes)
        {
            ListenerID = id;
            Bytes = bytes;
        }

        public object Clone()
        {
            return new Data(ListenerID, Bytes);
        }
    }
}